
package net.lab0.fractal.gui;


import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionAdapter;
import java.awt.event.MouseWheelEvent;
import java.awt.event.MouseWheelListener;
import java.awt.geom.Point2D;
import java.awt.image.BufferedImage;

import javax.swing.JComponent;
import javax.swing.event.EventListenerList;

import net.lab0.fractal.abstracts.AbstractManageableUI2DFractal;
import net.lab0.fractal.abstracts.DraggableZoomableFractalInterface;
import net.lab0.fractal.classes.RenderingCoordinates;
import net.lab0.fractal.classes.ViewCoordinates;
import net.lab0.fractal.event.FractalComponentMouseCoordinates;
import net.lab0.fractal.event.FractalComponentMouseCoordinatesListener;
import net.lab0.fractal.event.ProgressEvent;
import net.lab0.fractal.event.ProgressListener;
import net.lab0.fractal.fractals.mandelbrot.BasicMandelbrotFractal;


@SuppressWarnings("serial")
public class FractalComponent
extends JComponent
implements DraggableZoomableFractalInterface, ProgressListener
{
    protected AbstractManageableUI2DFractal fractal;
    protected Point                         mousePosition;
    protected double                        zoomRatio         = Math.pow(2, 1.0 / 3.0);
    protected EventListenerList             eventListenerList = new EventListenerList();
    
    public FractalComponent()
    {
        this(new BasicMandelbrotFractal(new RenderingCoordinates(0d, 0d, 4d, 4d, 512, 512), 128, 0.1f, 1000));
    }
    
    public FractalComponent(AbstractManageableUI2DFractal fractal)
    {
        super();
        if (fractal == null)
        {
            throw new NullPointerException("Fractal object can't be null.");
        }
        this.fractal = fractal;
        fractal.addProgressListener(this);
        initComponents();
    }
    
    public void addFractalComponentMouseCoordinatesListener(FractalComponentMouseCoordinatesListener listener)
    {
        eventListenerList.add(FractalComponentMouseCoordinatesListener.class, listener);
    }
    
    public void setFractal(AbstractManageableUI2DFractal fractal)
    {
        if (fractal != null)
        {
            this.fractal = fractal;
            fractal.addProgressListener(this);
        }
        else
        {
            throw new NullPointerException("AbstractUI2DFractalInterface in " + getClass().getSimpleName() + " can't be null.");
        }
    }
    
    private void initComponents()
    {
        addComponentListener(new ComponentAdapter()
        {
            @Override
            public void componentResized(ComponentEvent e)
            {
                if (getWidth() > 0 && getHeight() > 0)
                {
//					System.out.println("Resize : " + getWidth() + " X " + getHeight());
                    fractal.getRenderingCoordinates().setResolution(getSize());
                }
            }
        });
        
        addMouseMotionListener(new MouseMotionAdapter()
        {
            @Override
            public void mouseMoved(MouseEvent e)
            {
                fractalComponentMouseMoved(e);
            }
            
            @Override
            public void mouseDragged(MouseEvent e)
            {
                Point newPos = e.getPoint();
                int deltaX = newPos.x - mousePosition.x;
                int deltaY = newPos.y - mousePosition.y;
                
                ViewCoordinates vc = fractal.getViewCoordinates();
                vc.setxCenter(vc.getxCenter() - vc.getWidth() * deltaX / getWidth());
                vc.setyCenter(vc.getyCenter() + vc.getHeight() * deltaY / getHeight());
                
                mousePosition = newPos;
                fractal.restartComputation();
            }
        });
        
        addMouseWheelListener(new MouseWheelListener()
        {
            public void mouseWheelMoved(MouseWheelEvent e)
            {
                double scaling = Math.pow(zoomRatio, -e.getWheelRotation());
                
                if (e.getWheelRotation() < 0)
                {
                    double dx1 = (e.getPoint().x - getWidth() / 2);
                    double dy1 = (e.getPoint().y - getHeight() / 2);
                    
                    double dx2 = (e.getPoint().x - getWidth() / 2) / scaling;
                    double dy2 = (e.getPoint().y - getHeight() / 2) / scaling;
                    
                    RenderingCoordinates vc = fractal.getRenderingCoordinates();
                    vc.setxCenter(vc.getxCenter() - vc.getScaledWidth() * (dx2 - dx1) / getWidth());
                    vc.setyCenter(vc.getyCenter() + vc.getScaledHeight() * (dy2 - dy1) / getHeight());
                }
                fractal.getViewCoordinates().zoom(Math.pow(zoomRatio, e.getWheelRotation()));
                fractal.restartComputation();
            }
        });
    }
    
    protected void fractalComponentMouseMoved(MouseEvent e)
    {
        mousePosition = e.getPoint();
        fireMouseCoordinatesChanged(fractal.getRenderingCoordinates().toRenderingCoordinates(mousePosition));
    }
    
    @Override
    public ViewCoordinates getViewCoordinates()
    {
        return fractal.getViewCoordinates();
    }
    
    private void fireMouseCoordinatesChanged(Point2D.Double point)
    {
        FractalComponentMouseCoordinates event = new FractalComponentMouseCoordinates(this, point);
        for (FractalComponentMouseCoordinatesListener listener : eventListenerList.getListeners(FractalComponentMouseCoordinatesListener.class))
        {
            listener.mouseCoordinatesupdated(event);
        }
    }
    
    @Override
    public void paintComponent(Graphics g)
    {
        Graphics2D g2d = (Graphics2D) g;
        BufferedImage bi = fractal.renderImage();
        g2d.drawImage(bi, 0, 0, getWidth(), getHeight(), 0, 0, bi.getWidth(), bi.getHeight(), null);
    }
    
    @Override
    public void progression(ProgressEvent event)
    {
//		System.out.println(getClass() + " " + event.getProgression());
        repaint();
    }
    
//	@Override
//	public void setZoomStep(double zoomStep)
//	{
//		fractal.setZoomStep(zoomStep);
//	}
//	
//	@Override
//	public double getZoomStep()
//	{
//		return fractal.getZoomStep();
//	}
}
